#!/usr/local/bin/perl -w
use strict;
use warnings;
use utf8;

use IO::Async::Loop::Epoll;
use IO::Async::Stream;
use IO::Socket::INET;
use IO::Async::Timer::Countdown;

use Getopt::Long;
use JSON::XS;
use HTTP::Parser::XS qw(parse_http_response HEADERS_AS_HASHREF);

my $interval;
my $dump;
my $login;
my $inter;
my $log;
my $per;
my $count = 0;
my $sid;
my $port;
my $addr;
my $host;
my $path;
my $debug;
my $maxcount;
my $check_to;

my @options = (
	"interval=i"	=> \$interval,
	"dump"   	    => \$dump,
	"debug"   	    => \$debug,
	"interactive"	=> \$inter,
	"login=s"	    => \$login,
	"file=s"	    => \$log,
	"port=s"	    => \$port,
	"addr=s"	    => \$addr,
	"host=s"	    => \$host,
	"path=s"	    => \$path,
	"count=i"	    => \$maxcount,
	"timeout=i"	    => \$check_to,
);
get_param();
$addr ||= "127.0.0.1";
$port ||= 12345;
$host ||= "localhost";
$maxcount ||= 1000;
$check_to ||= 60;

unless($login){
	die "need login";
}
if($path){
	$path .= "/";
}else{
	$path = "";
}
$| =1;
binmode(STDOUT,":utf8");

if($log){
	open F,">",$log;
	close F;
}

my $loop = IO::Async::Loop::Epoll->new();

if($inter){
	make_interactive();
}

if($interval){
	make_cd()
}
send_msg(login => {login=>$login});

$loop->loop_forever();
print "\nExit normaly\n";

#############################################

sub send_msg {
	my ($command,$message) = @_;

	my $start = time();
	my $ts0 = sprintf "%02d:%02d:%02d", (localtime)[2,1,0];

	my $socket = IO::Socket::INET->new(
				PeerHost => $addr,
				PeerPort => $port,
				Type     => SOCK_STREAM,
			) || die "Cannot construct socket - $@";

	my $end = time();
	my $timer_on_resend;
	my $full_body ="";

	my $s = IO::Async::Stream->new(
		handle => $socket,

		on_read => sub {
			my ( $self, $buffref, $eof ) = @_;
			if($timer_on_resend){
				$timer_on_resend->stop;
				$loop->remove($timer_on_resend);
				$timer_on_resend = undef;
			}
			unless($$buffref){
				return 0;
			}
			print "-" x 40,"\n",$$buffref,"\n","RECV: ","=" x 40,"\n" if $debug;
			$full_body .= $$buffref;
			$$buffref = "";
			my($ret, $mv, $status, $mss, $headers) = parse_http_response($full_body, HEADERS_AS_HASHREF);
			if($ret > 0) {
				if($status == 200){
					my $body = substr($full_body,$ret);
					if($headers->{'content-length'} && $headers->{'content-length'} > length($body)){
						print "Partial data cl='",$headers->{'content-length'},"' bl='",length($body),"'\n" if $debug;
						return 1;
					}else{
						eval {
							my $msg = decode_json($body);
							if($msg->{type} eq "login"){
								$sid = $msg->{sid};
								print "LOGIN($msg->{ts}): ",$msg->{text},"\n" if $dump;
							}elsif($msg->{type} eq "error"){
								print "ERROR($msg->{ts}): ",$msg->{text},"\n" if $dump;
							}elsif($msg->{type} eq "system"){
								print "SYSTEM($msg->{ts}): ",$msg->{text},"\n" if $dump;
							}elsif($msg->{type} eq "list"){
								print "LISTSIZE=",scalar(@{$msg->{list}}),"\n" if $dump;
								for(@{$msg->{list}}){
									my $str = $_->{text};
									utf8::decode $str;
									print $_->{ts}," [$_->{from}] ",$str,"\n" if $dump;
								}
							}
						};
						print "Data parsed and worked, return 0 eof='$eof'\n" if $debug;
						$full_body = undef;
						return 0;
					}
				}else{
					print "BAD RESPONSE: ",$status," ",$mss,". Exited.\n";
					exit;
				}
			}elsif($ret == -2){
				print "Broken response. Exited.\n";
				exit;
			}else{
				print "Partial data\n" if $debug;
				return 1;
			}
		},

		on_closed => sub {
			my ( $self ) = @_;
			if($command eq "check"){
				if($dump){
					print "AFTER CHECK: ",ts(), " ","*" x 20,"\n" if $debug;
				}
				send_msg(check=>{});
			}elsif($command eq "login"){
				if($dump){
					print "AFTER LOGIN: ",ts(), " ","*" x 20,"\n" if $debug;
				}
				send_msg(check=>{});
			}elsif($command eq "message"){
				if($dump){
					print "AFTER MESSAGE: ",ts(), " ","*" x 20,"\n"  if $debug;
				}
			}
		},

	);
	$loop->add($s);
	
	$message->{ts} = ts();
	$message->{sid} = $sid if $sid;
	my $body = encode_json($message);
	my $to_send = sprintf(
"POST /%s$command HTTP/1.1
Content-Length: %d
Host: %s
Connection: close

%s",$path,length($body),$host,$body);
	$s->write($to_send);
	if($command eq "check"){
		$timer_on_resend = make_resend($s);
	}
	print("SEND:", "+" x 40,"\n",$to_send,"\n") if $debug;

	if($dump){
		my $lc;
		if($end - $start > 30){
			$lc = ("*" x 40)."Super Very Long connect ".($end - $start)." ".("*" x 25)."\n";
		}
		elsif($end - $start > 10){
			$lc = ("*" x 30)."Very Long connect ".($end - $start)." ".("*" x 25)."\n";
		}
		elsif($end - $start > 2){
			$lc = ("*" x 20)."Long connect ".($end - $start)." ".("*" x 25)."\n";
		}
		if($lc){
			print $lc;
			if($log){
				open F,">>",$log;
				print F $ts0," ",ts()," ",$lc;
				close F;
			}
		}
	}
}
sub make_interactive{
	$loop->add( IO::Async::Stream->new(
		read_handle => \*STDIN,
		on_read => sub {
			my ( $self, $buffref ) = @_;
			while( $$buffref =~ s/^(.*)\r?\n// ) {
				my $line = $1;
				send_msg( message => {text=>$line." ".ts()});
			}
			#print "$login>";
			return 0;
		},
	) );
	require IO::Async::Signal;
	$loop->add( IO::Async::Signal->new(
		name => 'INT',
		on_receipt => sub {
			print "\nSIGINT, will now quit\n";
			$loop->loop_stop;
		},
	) );
	$dump = 1;
	#print "$login>";
}

sub get_param {
	local $SIG{__WARN__} = sub {die join("\n",@_)};
	GetOptions(@options) or die "$!";
}

sub make_cd{
	my $timer = IO::Async::Timer::Countdown->new(
		delay => $interval,
	
		on_expire => sub {
			send_msg(message =>{text=>"my count = $count ".ts()});
			if(++$count > $maxcount){
				print "STOP LOOP $count\n" if $dump;
				$loop->loop_stop;
			}else{
				make_cd();
			}
		},
	);
	$loop->add( $timer );
	$timer->start;
}
sub make_resend{
	my $stream = shift;
	my $timer = IO::Async::Timer::Countdown->new(
		delay => $check_to,
	
		on_expire => sub {
			$stream->close;
			print "RESEND $check_to\n" if $debug;
		},
	);
	$loop->add( $timer );
	$timer->start;
	return $timer;
}
sub ts {
	return sprintf "%02d:%02d:%02d", (localtime)[2,1,0];
}
#############################################
